#!/usr/bin/python
# coding:utf-8

import re
import configparser
import codecs

from addon_manager.addon_exception import (
    AddOnConfigRequiredOptionLacked,
    AddOnConfigErrLanguageType,
    AddOnConfigErrType,
    AddOnConfigErrService,
    AddOnConfigErrName,
    AddOnConfigErrDescription,
    AddOnConfigErrUrl,
    InvalidAddonConfigFile,
    UnknownAddonConvention,
)
from addon_manager.addon_config import ADDON_VER_1, ADDON_VER_2, ADDON_VER_3
from addon_manager.addon_logger import AddOnLogger

# from addon_manager.addon_port_mgr import AddOnPortMgr

# move those to solitary configration file
ADDON_BASE_DIR = "/usr/etc/jkzuc/configs/JAKA/AddOns/"
ADDON_INFO_SEC_NAME = "AddOnInfo"
ADDON_INFO_OPT_CONVENTION = "Convention"
ADDON_INFO_OPT_BINNAME = "BinName"
ADDON_INFO_OPT_DESC = "description"
ADDON_INFO_OPT_VERSION = "Version"
ADDON_INFO_OPT_ENABLED = "Enabled"
ADDON_INFO_OPT_LANGUAGE_TYPE = "languageType"
ADDON_INFO_OPT_TYPE = "Type"
ADDON_INFO_OPT_SERVICE_ENABLED = "ServiceEnabled"
ADDON_INFO_OPT_SERVICE = "service"
ADDON_INFO_OPT_NAME = "name"
ADDON_INFO_OPT_URL = "URL"
ADDON_INFO_OPT_PORT = "portal"
ADDON_INFO_OPT_CREATOR = "creator"


class AddonCfgParser(object):
    """
    所有版本的AddOn配置文件parser父类, 并且parser需要实现parse函数
    """

    REGEX_URL = re.compile(
        r"^(?:http)s?://"  # http:// or https://
        r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|"  # domain...
        r"localhost|"  # localhost...
        r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"  # ...or ip
        r"(?::\d+)?"  # optional port
        r"(?:/?|[/?]\S+)$",
        re.IGNORECASE,
    )

    def __init__(self, config):
        self.config = config

    def parse_to(self, cfg, addon_info):
        """
        @param cfg: ConfigParser
        @param addon_info AddOnInfo
        """
        # this func should not be called
        raise NotImplementedError()

    def write_cfg_back(self, cfg, addon_info):
        """
        @param cfg: ConfigParser
        @param addon_info AddOnInfo
        """
        return False

    # def allocate_port(self, cfg, addon_info):
    #     addon_port_mgr = AddOnPortMgr.get_inst(self.config)
    #     value = addon_port_mgr.allocate_port(addon_info['name'])
    #     cfg.set(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_PORT, str(value))
    #     addon_info.set_iniconf('portal', value)

    # def release_port(self, addon_info):
    #     addon_port_mgr = AddOnPortMgr.get_inst(self.config)
    #     addon_port_mgr.release_port(addon_info['name'])


class Conv1AddOnCfgParser(AddonCfgParser):
    """
    AddOn V1 Parser
    """

    __REQUIRED_OPTIONS = set(["binname", "description", "version", "enabled", "type"])

    def __init__(self, config):
        AddonCfgParser.__init__(self, config)

    def check_if_options_satisfied(self, cfg):
        current = set(cfg.options(ADDON_INFO_SEC_NAME))
        if not self.__REQUIRED_OPTIONS <= current:
            raise AddOnConfigRequiredOptionLacked(self.__REQUIRED_OPTIONS - current)

    def parse_to(self, cfg, addon_info):
        self.check_if_options_satisfied(cfg)

        addon_info.set_iniconf("filename", addon_info["name"])

        value = str(cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_BINNAME))
        addon_info.set_iniconf("name", addon_info["name"])

        value = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_DESC)
        addon_info.set_iniconf("description", value)

        value = str(cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_VERSION))
        addon_info.set_iniconf("version", value)
        addon_info.set_iniconf("type", 0)

        service_name = addon_info["name"] + ".py"
        addon_info.set_iniconf("service", service_name)

        value = str(cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_ENABLED))
        addon_info.set_iniconf("serviceenabled", 0 if not value else 1)

        addon_info.set_iniconf("languagetype", "python2")

        addon_info.set_iniconf("convention", ADDON_VER_1)


class Conv2AddOnCfgParser(AddonCfgParser):
    """
    AddOn V2 Parser
    """

    __REQUIRED_OPTIONS = set(["convention", "description", "version", "type", "languagetype", "service", "serviceenabled"])

    __SUPPORTED_LANGUAGE_TYPE = set(["javascript", "python2", "python"])
    __SUPPORTED_TYPE = set([1, 2, 3, 4])  # 1 自定义服务; 2 自定义指令; 3 复合类型, 或许可以删除; 4 系统级AddOn

    def __init__(self, config):
        AddonCfgParser.__init__(self, config)

    def __check_if_options_satisfied(self, cfg):
        current = set(cfg.options(ADDON_INFO_SEC_NAME))
        # print('current', current)
        if not self.__REQUIRED_OPTIONS <= current:
            raise AddOnConfigRequiredOptionLacked(self.__REQUIRED_OPTIONS - current)

    def _check_if_languagetype_satisfied(self, languagetype):
        if not languagetype in self.__SUPPORTED_LANGUAGE_TYPE:
            raise AddOnConfigErrLanguageType(languagetype)

    def __check_if_type_satisfied(self, addon_type):
        if not addon_type in self.__SUPPORTED_TYPE:
            raise AddOnConfigErrType()

    def __check_if_service_satisfied(self, service):
        if not (service and len(service) < 50):
            raise AddOnConfigErrService()

    def __check_if_name_satisfied(self, name):
        if not (name and len(name) < 40):
            raise AddOnConfigErrName()

    def __check_if_decription_satisfied(self, desc):
        if not (desc and len(desc) < 40):
            raise AddOnConfigErrDescription()

    def __check_if_url_satisfied(self, url):
        if not self.REGEX_URL.match(url):
            raise AddOnConfigErrUrl()

    def __check_if_has_option(self, cfg, opt):
        return cfg.has_option(ADDON_INFO_SEC_NAME, opt)

    def parse_to(self, cfg, addon_info):
        self.__check_if_options_satisfied(cfg)

        addon_info.set_iniconf("filename", addon_info["name"])

        # required
        value = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_LANGUAGE_TYPE)
        self._check_if_languagetype_satisfied(value)
        addon_info.set_iniconf("languagetype", value)

        value = cfg.getint(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_TYPE)
        self.__check_if_type_satisfied(value)
        addon_info.set_iniconf("type", value)

        value = cfg.getboolean(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_SERVICE_ENABLED)
        addon_info.set_iniconf("serviceenabled", 0 if not value else 1)

        value = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_SERVICE)
        self.__check_if_service_satisfied(value)
        addon_info.set_iniconf("service", value)

        # value = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_NAME)
        self.__check_if_name_satisfied(addon_info["name"])
        addon_info.set_iniconf("name", addon_info["name"])

        # UnicodeEncodeError: 'ascii' codec can't encode characters in position 2-4: ordinal not in range(128)
        value = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_DESC)
        self.__check_if_decription_satisfied(value)
        addon_info.set_iniconf("description", value)

        value = str(cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_VERSION))
        addon_info.set_iniconf("version", value)

        value = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_CONVENTION)
        addon_info.set_iniconf("convention", value)

        addon_info.set_iniconf("AFWQGQWEQWTQWT", value)

        # optional
        if self.__check_if_has_option(cfg, ADDON_INFO_OPT_URL):
            value = str(cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_URL))
            self.__check_if_url_satisfied(value)
            addon_info.set_iniconf("url", value)

        if self.__check_if_has_option(cfg, ADDON_INFO_OPT_CREATOR):
            value = str(cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_CREATOR))
            addon_info.set_iniconf("creator", value)

    def write_cfg_back(self, cfg, addon_info):
        """
        @param cfg: ConfigParser
        @param addon_info AddOnInfo
        @return should_write_to_ini
        """
        should_write_to_ini = False

        old_val = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_PORT)
        new_val = str(addon_info.get_iniconf("portal"))
        if str(old_val) != str(new_val):
            AddOnLogger().info("write port {} from {} to {}".format(addon_info["inifile"], old_val, new_val))
            cfg.set(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_PORT, new_val)
            should_write_to_ini = True

        old_val = cfg.get(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_NAME)
        new_val = str(addon_info.get_iniconf("name"))
        if old_val != new_val:
            cfg.set(ADDON_INFO_SEC_NAME, ADDON_INFO_OPT_NAME, new_val)
            should_write_to_ini = True

        return should_write_to_ini


class Conv3AddOnCfgParser(Conv2AddOnCfgParser):

    __SUPPORTED_LANGUAGE_TYPE = ["node-red", "node"]

    def __init__(self, config):
        Conv2AddOnCfgParser.__init__(self, config)

    def _check_if_languagetype_satisfied(self, languagetype):
        if (not languagetype in self.__SUPPORTED_LANGUAGE_TYPE) and (Conv2AddOnCfgParser._check_if_languagetype_satisfied(self, languagetype)):
            raise AddOnConfigErrLanguageType(languagetype)


class AddOnCfgParseWrapper(object):
    __ADDON_PARSER = {ADDON_VER_1: Conv1AddOnCfgParser, ADDON_VER_2: Conv2AddOnCfgParser, ADDON_VER_3: Conv3AddOnCfgParser}

    def __init__(self, config):
        """
        config: AddOnConfig, 存放AddOn中的配置信息
        """
        self.config = config

    def __check_addoninfo_sec(self, cfg):
        if not cfg.has_section(ADDON_INFO_SEC_NAME):
            raise InvalidAddonConfigFile()

    def __check_if_addoninfo_have_opt(self, cfg, option):
        return cfg.has_option(ADDON_INFO_SEC_NAME, option)

    def __get_from_addoninfo(self, cfg, option):
        return cfg.get(ADDON_INFO_SEC_NAME, option)

    def __get_addon_convention(self, cfg):
        if not self.__check_if_addoninfo_have_opt(cfg, ADDON_INFO_OPT_CONVENTION):
            return ADDON_VER_1

        conv = self.__get_from_addoninfo(cfg, ADDON_INFO_OPT_CONVENTION)
        if conv in self.__ADDON_PARSER.keys():
            return conv

        raise UnknownAddonConvention(conv)

    def __tool_get_dirname(self, abspath):
        return abspath.replace(ADDON_BASE_DIR, "")

    def __get_parser(self, convention):
        """
        根据AddOn版本,选择合适的解析器解析AddOn配置文件
        """
        parser = self.__ADDON_PARSER[convention]
        return parser(self.config)

    def parse_to(self, addon_info):
        """
        @param addon_info AddonInfo实例
        """
        config_parser = configparser.ConfigParser()
        config_parser.read(addon_info["inifile"], encoding="utf-8")

        self.__check_addoninfo_sec(config_parser)
        convention = self.__get_addon_convention(config_parser)
        parser = self.__get_parser(convention)
        addon_info.set_convention(convention)

        parser.parse_to(config_parser, addon_info)

        # # 由控制器分配端口
        # parser.allocate_port(config_parser, addon_info)

    def write_cfg_back(self, addon_info):
        # AddOn V1/V2/V3 端口由控制器分配，所以需要回写配置文件
        config_parser = configparser.ConfigParser()
        with codecs.open(addon_info["inifile"], "r", encoding="utf-8") as inifile:
            config_parser.read_file(inifile)

        convention = addon_info.get_convention()
        parser = self.__get_parser(convention)
        should_write_to_ini = parser.write_cfg_back(config_parser, addon_info)

        if should_write_to_ini:
            with codecs.open(addon_info["inifile"], "w", encoding="utf-8") as inifile:
                config_parser.write(inifile)

    def release_from(self, addon_info):
        pass
        # convention = addon_info.get_convention()
        # parser = self.__get_parser(convention)
        # parser.release_port(addon_info)


__all__ = ["AddOnCfgParseWrapper"]

if __name__ == "__main__":
    from addon_manager.addon_config import AddOnConfig
    from addon_manager.addon_info import AddOnInfo

    def test_addoncfg_parser_wrapper():
        parser = AddOnCfgParseWrapper(AddOnConfig.inst())
        addoninfo = AddOnInfo(AddOnConfig.inst(), name="io_event_triger")
        parser.parse_to(addoninfo)
        print("[Test_AddOnCfgParseWrapper] addoninfo: {}".format(addoninfo))

    test_addoncfg_parser_wrapper()
